package com.rengwuxian.coursecoroutines._4_flow

import com.rengwuxian.coursecoroutines.common.Contributor
import com.rengwuxian.coursecoroutines.common.gitHub
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.FileWriter
import kotlin.coroutines.EmptyCoroutineContext

/**
 * Channel API详解
 * send()/trySend()、receive()/tryReceive()、Channel的容量和缓冲溢出、关闭和取消
 *
 * 对Channel对象进行for循环遍历的形式：（实现了Iterable接口）
 *   launch {
 *     for (data in channel) {
 *       println("Contributors: $data")
 *     }
 *     /*while (isActive) {
 *       val contributors = channel.receive()
 *       println("Contributors: $contributors")
 *     }*/
 *   }
 *   如果没有元素，则自动挂起，直到下一个元素到来。
 *
 * channel.send(gitHub.contributors("square", "retrofit"))
 * send是一个挂起函数，如果队列满了的时候，就会挂起，直到有位置空出来。
 *
 * Channel中的队列的默认长度是0，没有缓冲区。-》RENDEZVOUS（碰面约会，必须直接相见来拿数据）。 --》 一个send对应一个receive
 * 如果想配置缓冲区大小，配置Channel的第一个参数capacity，队列的尺寸。
 * val channel = Channel<List<Contributor>>(8)
 * 协程提供的长度有：UNLIMITED-》INT最大值。 BUFFERED-》64。 CONFLATED-》1。
 * 大多数情况，自己根据实际情况手动填写该值。
 *
 * Channel的第二个参数：缓冲区的处理策略，除了默认的BufferOverflow.SUSPEND之外，还有BufferOverflow.DROP_OLDEST（扔队首元素，然后替换到队首）
 *
 * val channel = Channel<List<Contributor>>(1, BufferOverflow.DROP_OLDEST)
 * val channel = Channel<List<Contributor>>(CONFLATED)
 * 这两种写法是等价的，缓冲区为1，永远保持最新。
 *
 * Channel的关闭：close()属于SendChannel的、cancel()属于ReceiveChannel的。
 *
 * close()从Channel的发送端关闭Channel
 * SendChannel中有一个属性：
 *    isClosedForSend，状态标记，标记还能不能发送数据的。在Channel刚开始创建的时候是false，当你调用close()之后，这个值就变成true了，从这之后，就不允许再调用send()了，否则就会抛出CloseSendChannelException异常。
 *    所有协程都不能发送了。是从Channel的角度来关闭（整个业务角度），不是某一个协程。
 *    isClosedForSend只能管住不能再发送，但管不住接收的，在close()之后，如果有协程调用receive()来接收数据，它依然是能获取的缓冲区和正在挂起的send()函数。
 *    在close()之前就send()的挂起函数，不会抛异常，也是可以通过receive()来获取到的。当所有数据都接收完之后，ReceiveChannel就会变的无用了。
 *
 * ReceiveChannel中有一个类似的属性：
 *    isClosedForReceive,标记还能不能接收数据的。初始值是false，如果调用了close()还存在缓冲或者suspend的数据，等这些数据被接收完了之后，这个值才会被自动置为true。
 *    这个值在为true后还继续receive的，就会抛出ClosedReceiveChannelException异常。
 *
 * cancel()从Channel的接收端关闭Channel(不在需要新数据了)
 * channel.cancel() ： 会直接将 isClosedForSend 和 isClosedForReceive全都改成true，就是send()和receive()全都不能调用了。
 * 缓冲区数据，已经通过send()的挂起，在缓冲区外面等待的数据，全部没有用，直接抛异常。
 * 会抛出 CancellationException ，用此，可以用来判断 Channel 关闭的原因。
 *
 * 可以使用自定义的异常来定义 send() 和 receive() 抛出的异常类型。
 * 比如close定制异常：channel.close(IllegalStateException("Data error!"))
 *
 * Channel关闭的补充：
 * 如果Channel发送的是一个打开的文件流，使用完文件流需要关闭，如果channel关闭了，这些数据就会被丢到外太空，造成资源浪费。
 * val fileChannel = Channel<FileWriter>() { it.close() }
 * 可以先关闭，再丢弃。上面的回调函数：onUndeliveredElement
 *
 * trySend() 和 tryReceive() 非挂起函数，瞬时返回。
 * channel.trySend(gitHub.contributors("square", "retrofit"))
 * channel.tryReceive()
 * 返回值为 ChannelResult。 isSuccess、isFailure、isClosed、getOrNull()、getOrThrow()、exception等
 * 根据不同的状态，返回不同的值。不会主动抛异常，由自己处理异常。
 *
 * receiveCatching() 挂起式函数 + 返回值是ChannelResult.
 * 相当于是receive()和tryReceive()的结合体。
 */
fun main() = runBlocking<Unit> {
  val scope = CoroutineScope(EmptyCoroutineContext)
  val fileChannel = Channel<FileWriter>() { it.close() }
  fileChannel.send(FileWriter("test.txt"))
//  val channel = Channel<List<Contributor>>(8, BufferOverflow.DROP_OLDEST)
//  val channel = Channel<List<Contributor>>(1, BufferOverflow.DROP_OLDEST)
  val channel = Channel<List<Contributor>>(CONFLATED)
  scope.launch {
    channel.send(gitHub.contributors("square", "retrofit"))
    channel.close()
    channel.close(IllegalStateException("Data error!"))
    channel.receive()
    channel.receive()
    channel.send(gitHub.contributors("square", "retrofit"))
    channel.trySend(gitHub.contributors("square", "retrofit"))
    channel.tryReceive()
  }
  launch {
    for (data in channel) {
      println("Contributors: $data")
    }
    /*while (isActive) {
      val contributors = channel.receive()
      println("Contributors: $contributors")
    }*/
  }
  delay(1000)
  channel.cancel()
  delay(10000)
}